package gp;



/**
 * @author Beacom
 * @version 1.0
 * @created 18-Feb-2008 12:43:45 PM
 */
public class Organism implements Comparable<Organism> {

	private OrganismNode rootOrganismNode;
	private double error;
	
	public Organism() throws Exception{
		throw new Exception("Default Organism constructor invoked.");
	}
	
	/***************************************************************************************************************************/
	public Organism (int depth, String[] variables){ // This can be refactored since multiple variable requirement was dropped.
		rootOrganismNode = new OperatorNode().selectRandomOperation().generateRandomChildren(depth - 1,variables);
	}
	
	public Organism(Organism original) {
		this.rootOrganismNode = original.rootOrganismNode.createCopy();
		this.error = original.error;
	}
	
	public void setVariable(String var, double val){
		rootOrganismNode.setVariable(var,val);
	}
	
	public void finalize() throws Throwable {

	}
	
	public Organism mutate() {
		Organism o = new Organism(this);
		o.rootOrganismNode.mutate(); 
		return o;
	}
	
	public Organism crossover(Organism otherParent) {
		Organism o = new Organism(this);
		double randomNumber = 0.0;
		
		OrganismNode swapNode = otherParent.rootOrganismNode;
		boolean swapTargetSelected = false;
		while(!swapTargetSelected) {
			randomNumber = Math.random();
			
			if(swapNode.getClass() == OperatorNode.class) {
				if(randomNumber < 0.75) {
					// The following provides a slight denominator swap bias.
					if(randomNumber < 0.35) {
						swapNode = ((OperatorNode) swapNode).getLeftChild();
					} else {
						swapNode = ((OperatorNode) swapNode).getRightChild();
					}
				} else {
					swapTargetSelected = true;
				}
			} else {
				swapTargetSelected = true;
			}
		}
		
		OrganismNode targetNode = o.rootOrganismNode;
		boolean swapDone = false;
		while(!swapDone) {
			randomNumber = Math.random();

			if(randomNumber < 0.50 ) {
				// The following provides a slight denominator swap bias.
				if(randomNumber < 0.35) {
					if(((OperatorNode) targetNode).getLeftChild().getClass() == OperatorNode.class) {
						targetNode = ((OperatorNode) targetNode).getLeftChild();
					} else {
						((OperatorNode) targetNode).setLeftChild(swapNode);
						swapDone = true;	
					}
				} else {
					if(((OperatorNode) targetNode).getRightChild().getClass() == OperatorNode.class) {
						targetNode = ((OperatorNode) targetNode).getRightChild();
					} else {
						((OperatorNode) targetNode).setRightChild(swapNode);
						swapDone = true;
					}
				}
			} else {
				// crossover!
				// The following provides a slight denominator swap bias.
				if(randomNumber < 0.35) {
					((OperatorNode) targetNode).setLeftChild(swapNode);
					swapDone = true;
				} else {
					((OperatorNode) targetNode).setRightChild(swapNode);
					swapDone = true;
				}
			}
		}
		
		//String[] variables = {"x"};
		return o; // This method needs to be rewritten to create a new Organism from two existing Organisms.
	}

	/**
	 * 
	 * @param inputDataSet
	 * @throws Exception 
	 */
	public void evaluate(DataSet inputDataSet) throws Exception{
		error = 0.0;
		for(DataEntry entry : inputDataSet.getDataEntryVector()) {
			rootOrganismNode.setVariable("x", entry.getVariable("x"));
			double result = rootOrganismNode.evaluate();
			double targetResult = entry.getOutput();
			error += Math.abs(targetResult - result); // We may want to tweak how this is calculated.
			//error = Math.round(100*error)/100;
			//error += targetResult - result;
			//System.out.println("(result, targetResult) = (" + result + " , " + targetResult + ")");
		}
		//System.out.println("Organism evaluated, error: " + error);
	}
	
	/**
	 * Overloading the method above.
	 * @param x - pass in x value to be evaluated
	 * @return - returns the value
	 */
	public double evaluate(double x) throws Exception {
		rootOrganismNode.setVariable("x", x);
		return rootOrganismNode.evaluate();
	}

	public String toString(){
		return rootOrganismNode.toString();
	}
	
	public int compareTo(Organism otherOrganism) {
		if(otherOrganism == null) {
			throw new NullPointerException();
		}
		if(this.error < otherOrganism.error) {
			return -1;
		} else if (this.error == otherOrganism.error) {
			return 0;
		} else {
			return 1;
		}
	}

	public double getError() {
		return error;
	}

	public void setError(double error) {
		this.error = error;
	}

	public OrganismNode getRootOrganismNode() {
		return rootOrganismNode;
	}

	public void setRootOrganismNode(OrganismNode rootOrganismNode) {
		this.rootOrganismNode = rootOrganismNode;
	}
}